
#include "testRTSPClient.h"

UsageEnvironment &operator<<(UsageEnvironment &env,
                             const RTSPClient &rtspClient) {
  return env << "[URL:\"" << rtspClient.url() << "\"]: ";
}

UsageEnvironment &operator<<(UsageEnvironment &env,
                             const MediaSubsession &subsession) {
  return env << subsession.mediumName() << "/" << subsession.codecName();
}

class StreamClientState {
 public:
  StreamClientState();
  virtual ~StreamClientState();

 public:
  MediaSubsessionIterator *iter;
  MediaSession *session;
  MediaSubsession *subsession;
  TaskToken streamTimerTask;
  double duration;
};

class ourRTSPClient : public RTSPClient {
 public:
  static ourRTSPClient *createNew(UsageEnvironment &env, char const *rtspURL,
                                  int verbosityLevel = 0,
                                  char const *applicationName = NULL,
                                  portNumBits tunnelOverHTTPPortNum = 0);

 protected:
  ourRTSPClient(UsageEnvironment &env, char const *rtspURL, int verbosityLevel,
                char const *applicationName, portNumBits tunnelOverHTTPPortNum);
  virtual ~ourRTSPClient();

 public:
  StreamClientState scs;
};

class DummySink : public MediaSink {
 public:
  static DummySink *createNew(UsageEnvironment &env,
                              MediaSubsession &subsession,
                              char const *streamId = NULL);

 private:
  DummySink(UsageEnvironment &env, MediaSubsession &subsession,
            char const *streamId);
  virtual ~DummySink();

  static void afterGettingFrame(void *clientData, unsigned frameSize,
                                unsigned numTruncatedBytes,
                                struct timeval presentationTime,
                                unsigned durationInMicroseconds);
  void afterGettingFrame(unsigned frameSize, unsigned numTruncatedBytes,
                         struct timeval presentationTime,
                         unsigned durationInMicroseconds);

 private:
  virtual Boolean continuePlaying();

 private:
  u_int8_t *fReceiveBufferTittle;
  u_int32_t u32MultiSliceOffset;
  u_int8_t *fReceiveBuffer;
  MediaSubsession &fSubsession;

  char *fStreamId;

 public:
  SPropRecord *sps;
  int sPropRecordsnum[4];
  char sPropRecords[4][256];
};

#define RTSP_CLIENT_VERBOSITY_LEVEL 1

static unsigned rtspClientCount = 0;

void openURL(UsageEnvironment &env, char const *progName, char const *rtspURL) {
  RTSPClient *rtspClient = ourRTSPClient::createNew(
      env, rtspURL, RTSP_CLIENT_VERBOSITY_LEVEL, progName);
  if (rtspClient == NULL) {
    env << "Failed to create a RTSP client for URL \"" << rtspURL
        << "\": " << env.getResultMsg() << "\n";
    return;
  }
  ++rtspClientCount;
  rtspClient->sendDescribeCommand(continueAfterDESCRIBE);
}

// Implementation of the RTSP 'response handlers':

void continueAfterDESCRIBE(RTSPClient *rtspClient, int resultCode,
                           char *resultString) {
  do {
    UsageEnvironment &env = rtspClient->envir();                  // alias
    StreamClientState &scs = ((ourRTSPClient *)rtspClient)->scs;  // alias

    if (resultCode != 0) {
      env << *rtspClient << "Failed to get a SDP description: " << resultString
          << "\n";
      delete[] resultString;
      break;
    }

    char *const sdpDescription = resultString;
    env << *rtspClient << "Got a SDP description:\n" << sdpDescription << "\n";

    // Create a media session object from this SDP description:
    scs.session = MediaSession::createNew(env, sdpDescription);
    delete[] sdpDescription;  // because we don't need it anymore
    if (scs.session == NULL) {
      env << *rtspClient
          << "Failed to create a MediaSession object from the SDP "
             "description: "
          << env.getResultMsg() << "\n";
      break;
    } else if (!scs.session->hasSubsessions()) {
      env << *rtspClient
          << "This session has no media subsessions (i.e., no \"m=\" "
             "lines)\n";
      break;
    }

    scs.iter = new MediaSubsessionIterator(*scs.session);
    setupNextSubsession(rtspClient);
    return;
  } while (0);

  shutdownStream(rtspClient);
}

static Boolean request_type = False;
void SET_REQUEST_STREAMING_OVER_TYPE(EASY_RTP_CONNECT_TYPE istcp) {
  if (istcp == EASY_RTP_OVER_TCP) {
    request_type = True;
  } else {
    request_type = False;
  }
}
void setupNextSubsession(RTSPClient *rtspClient) {
  UsageEnvironment &env = rtspClient->envir();                  // alias
  StreamClientState &scs = ((ourRTSPClient *)rtspClient)->scs;  // alias

  scs.subsession = scs.iter->next();
  if (scs.subsession != NULL) {
    if (!scs.subsession->initiate()) {
      env << *rtspClient << "Failed to initiate the \"" << *scs.subsession
          << "\" subsession: " << env.getResultMsg() << "\n";
      setupNextSubsession(
          rtspClient);  // give up on this subsession; go to the next one
    } else {
      env << *rtspClient << "Initiated the \"" << *scs.subsession
          << "\" subsession (";

      if (strstr(scs.subsession->codecName(), "H264")) {
        // env << "start2222264" << scs.subsession->codecName() << "\n";
        // start_himi(OT_PT_H264);
      } else if (strstr(scs.subsession->codecName(), "H265")) {
        // env << "start2222265" << scs.subsession->codecName() << "\n";
        // start_himi(OT_PT_H265);
      }
      if (scs.subsession->rtcpIsMuxed()) {
        env << "client port " << scs.subsession->clientPortNum();
      } else {
        env << "client ports " << scs.subsession->clientPortNum() << "-"
            << scs.subsession->clientPortNum() + 1;
      }
      env << ")\n";

      rtspClient->sendSetupCommand(*scs.subsession, continueAfterSETUP, False,
                                   true);
    }
    return;
  }

  if (scs.session->absStartTime() != NULL) {
    rtspClient->sendPlayCommand(*scs.session, continueAfterPLAY,
                                scs.session->absStartTime(),
                                scs.session->absEndTime());
  } else {
    scs.duration = scs.session->playEndTime() - scs.session->playStartTime();
    rtspClient->sendPlayCommand(*scs.session, continueAfterPLAY);
  }
}

void continueAfterSETUP(RTSPClient *rtspClient, int resultCode,
                        char *resultString) {
  do {
    UsageEnvironment &env = rtspClient->envir();                  // alias
    StreamClientState &scs = ((ourRTSPClient *)rtspClient)->scs;  // alias

    if (resultCode != 0) {
      env << *rtspClient << "Failed to set up the \"" << *scs.subsession
          << "\" subsession: " << resultString << "\n";
      break;
    }

    env << *rtspClient << "Set up the \"" << *scs.subsession
        << "\" subsession (";
    if (scs.subsession->rtcpIsMuxed()) {
      env << "client port " << scs.subsession->clientPortNum();
    } else {
      env << "client ports " << scs.subsession->clientPortNum() << "-"
          << scs.subsession->clientPortNum() + 1;
    }
    env << ")\n";

    unsigned int numSPropRecord;

    scs.subsession->sink =
        DummySink::createNew(env, *scs.subsession, rtspClient->url());

    // perhaps use your own custom "MediaSink" subclass instead
    if (scs.subsession->sink == NULL) {
      env << *rtspClient << "Failed to create a data sink for the \""
          << *scs.subsession << "\" subsession: " << env.getResultMsg() << "\n";
      break;
    }

    env << *rtspClient << "Created a data sink for the \"" << *scs.subsession
        << "\" subsession\n";
    scs.subsession->miscPtr =
        rtspClient;  // a hack to let subsession handler functions get the
                     // "RTSPClient" from the subsession
    scs.subsession->sink->startPlaying(*(scs.subsession->readSource()),
                                       subsessionAfterPlaying, scs.subsession);
    // Also set a handler to be called if a RTCP "BYE" arrives for this
    // subsession:
    if (scs.subsession->rtcpInstance() != NULL) {
      scs.subsession->rtcpInstance()->setByeWithReasonHandler(
          subsessionByeHandler, scs.subsession);
    }
  } while (0);
  delete[] resultString;

  // Set up the next subsession, if any:
  setupNextSubsession(rtspClient);
}

void continueAfterPLAY(RTSPClient *rtspClient, int resultCode,
                       char *resultString) {
  Boolean success = False;

  do {
    UsageEnvironment &env = rtspClient->envir();                  // alias
    StreamClientState &scs = ((ourRTSPClient *)rtspClient)->scs;  // alias

    if (resultCode != 0) {
      env << *rtspClient << "Failed to start playing session: " << resultString
          << "\n";
      break;
    }

    if (scs.duration > 0) {
      unsigned const delaySlop = 2;
      scs.duration += delaySlop;
      unsigned uSecsToDelay = (unsigned)(scs.duration * 1000000);
      scs.streamTimerTask = env.taskScheduler().scheduleDelayedTask(
          uSecsToDelay, (TaskFunc *)streamTimerHandler, rtspClient);
    }

    env << *rtspClient << "Started playing session";
    if (scs.duration > 0) {
      env << " (for up to " << scs.duration << " seconds)";
    }
    env << "...\n";

    success = True;
  } while (0);
  delete[] resultString;

  if (!success) {
    // An unrecoverable error occurred with this stream.
    shutdownStream(rtspClient);
  }
}

// Implementation of the other event handlers:

void subsessionAfterPlaying(void *clientData) {
  MediaSubsession *subsession = (MediaSubsession *)clientData;
  RTSPClient *rtspClient = (RTSPClient *)(subsession->miscPtr);

  // Begin by closing this subsession's stream:
  Medium::close(subsession->sink);
  subsession->sink = NULL;

  // Next, check whether *all* subsessions' streams have now been closed:
  MediaSession &session = subsession->parentSession();
  MediaSubsessionIterator iter(session);
  while ((subsession = iter.next()) != NULL) {
    if (subsession->sink != NULL) return;  // this subsession is still active
  }

  // All subsessions' streams have now been closed, so shutdown the client:
  shutdownStream(rtspClient);
}

void subsessionByeHandler(void *clientData, char const *reason) {
  MediaSubsession *subsession = (MediaSubsession *)clientData;
  RTSPClient *rtspClient = (RTSPClient *)subsession->miscPtr;
  UsageEnvironment &env = rtspClient->envir();  // alias

  env << *rtspClient << "Received RTCP \"BYE\"";
  if (reason != NULL) {
    env << " (reason:\"" << reason << "\")";
    delete[](char *) reason;
  }
  env << " on \"" << *subsession << "\" subsession\n";

  // Now act as if the subsession had closed:
  subsessionAfterPlaying(subsession);
}

void streamTimerHandler(void *clientData) {
  ourRTSPClient *rtspClient = (ourRTSPClient *)clientData;
  StreamClientState &scs = rtspClient->scs;  // alias

  scs.streamTimerTask = NULL;

  shutdownStream(rtspClient);
}

void shutdownStream(RTSPClient *rtspClient, int exitCode) {
  UsageEnvironment &env = rtspClient->envir();                  // alias
  StreamClientState &scs = ((ourRTSPClient *)rtspClient)->scs;  // alias

  // First, check whether any subsessions have still to be closed:
  if (scs.session != NULL) {
    Boolean someSubsessionsWereActive = False;
    MediaSubsessionIterator iter(*scs.session);
    MediaSubsession *subsession;

    while ((subsession = iter.next()) != NULL) {
      if (subsession->sink != NULL) {
        Medium::close(subsession->sink);
        subsession->sink = NULL;

        if (subsession->rtcpInstance() != NULL) {
          subsession->rtcpInstance()->setByeHandler(NULL, NULL);
        }

        someSubsessionsWereActive = True;
      }
    }

    if (someSubsessionsWereActive) {
      rtspClient->sendTeardownCommand(*scs.session, NULL);
    }
  }

  env << *rtspClient << "Closing the stream.\n";
  Medium::close(rtspClient);

  if (--rtspClientCount == 0) {
    extern char eventLoopWatchVariable;
    eventLoopWatchVariable = 1;
  }
}

// Implementation of "ourRTSPClient":

ourRTSPClient *ourRTSPClient::createNew(UsageEnvironment &env,
                                        char const *rtspURL, int verbosityLevel,
                                        char const *applicationName,
                                        portNumBits tunnelOverHTTPPortNum) {
  return new ourRTSPClient(env, rtspURL, verbosityLevel, applicationName,
                           tunnelOverHTTPPortNum);
}

ourRTSPClient::ourRTSPClient(UsageEnvironment &env, char const *rtspURL,
                             int verbosityLevel, char const *applicationName,
                             portNumBits tunnelOverHTTPPortNum)
    : RTSPClient(env, rtspURL, verbosityLevel, applicationName,
                 tunnelOverHTTPPortNum, -1) {}

ourRTSPClient::~ourRTSPClient() {}

// Implementation of "StreamClientState":

StreamClientState::StreamClientState()
    : iter(NULL),
      session(NULL),
      subsession(NULL),
      streamTimerTask(NULL),
      duration(0.0) {}

StreamClientState::~StreamClientState() {
  delete iter;
  if (session != NULL) {
    // We also need to delete "session", and unschedule "streamTimerTask"
    // (if set)
    UsageEnvironment &env = session->envir();  // alias

    env.taskScheduler().unscheduleDelayedTask(streamTimerTask);
    Medium::close(session);
  }
}

// Implementation of "DummySink":

// Even though we're not going to be doing anything with the incoming data, we
// still need to receive it. Define the size of the buffer that we'll use:
#define DUMMY_SINK_RECEIVE_BUFFER_SIZE 5000000

DummySink *DummySink::createNew(UsageEnvironment &env,
                                MediaSubsession &subsession,
                                char const *streamId) {
  return new DummySink(env, subsession, streamId);
}

DummySink::DummySink(UsageEnvironment &env, MediaSubsession &subsession,
                     char const *streamId)
    : MediaSink(env), fSubsession(subsession) {
  fStreamId = strDup(streamId);

  fReceiveBufferTittle = new u_int8_t[DUMMY_SINK_RECEIVE_BUFFER_SIZE + 4];
  fReceiveBuffer = fReceiveBufferTittle + 4;
  fReceiveBufferTittle[0] = 0;
  fReceiveBufferTittle[1] = 0;
  fReceiveBufferTittle[2] = 0;
  fReceiveBufferTittle[3] = 1;
  u32MultiSliceOffset = 0;
}

DummySink::~DummySink() {
  // delete[] fReceiveBufferadd4;
  delete[] fReceiveBufferTittle;
  delete[] fStreamId;
}

void DummySink::afterGettingFrame(void *clientData, unsigned frameSize,
                                  unsigned numTruncatedBytes,
                                  struct timeval presentationTime,
                                  unsigned durationInMicroseconds) {
  DummySink *sink = (DummySink *)clientData;
  sink->afterGettingFrame(frameSize, numTruncatedBytes, presentationTime,
                          durationInMicroseconds);
}

// If you don't want to see debugging output for each received frame, then
// comment out the following line:
#define DEBUG_PRINT_EACH_RECEIVED_FRAME 1
#define DEBUG_PRINT_NPT 1

#include <iostream>
void *userptr_ = NULL;
RTSPSourceCallBack callback_ = NULL;
void SetFrameCallback(RTSPSourceCallBack callback, void *userptr) {
  callback_ = callback;
  userptr_ = userptr;
}
bool firststart = true;

void DummySink::afterGettingFrame(unsigned frameSize,
                                  unsigned numTruncatedBytes,
                                  struct timeval presentationTime,
                                  unsigned /*durationInMicroseconds*/) {
  // printf("frameSize:%d\n", frameSize);
  std::string tmpMediaStr;
  std::string tmpCodecStr;
  tmpMediaStr = fSubsession.mediumName();
  tmpCodecStr = fSubsession.codecName();

  if (tmpMediaStr == "audio" /*&& tmpCodecStr == "L16"*/) {
    if (callback_) {
      EASY_FRAME_INFO_T frameinfo;
      memset(&frameinfo, 0x00, sizeof(EASY_FRAME_INFO_T));
      frameinfo.framesize = frameSize;
      frameinfo.frameType = EASY_RTP_AUDIO;
      frameinfo.frameform = EASY_RTP_L16;
      frameinfo.framebuff = fReceiveBuffer;
      frameinfo.tv_sec = presentationTime.tv_sec;
      frameinfo.tv_usec = presentationTime.tv_usec;
      callback_(frameinfo, userptr_);
    }

  } else if (tmpMediaStr == "video") {
    unsigned char *pCharHolder = NULL;
    unsigned int *pIntHolder = NULL;
    bool bFirstFrame = false;
    bool bFrameEnd = false;
    unsigned char u8NaluType = 0;
    bool is264 = false;
    bool Ifranme = false;
    pCharHolder = fReceiveBuffer;
    pIntHolder = (unsigned int *)pCharHolder;
    if (tmpCodecStr == "H264") {
      is264 = true;
      u8NaluType = pCharHolder[0] & 0x1F;
      if (u8NaluType == 5) {
        Ifranme = true;
      }

    } else if (tmpCodecStr == "H265") {
      u8NaluType = (pCharHolder[0] & 0x7E) >> 1;
      if (u8NaluType == 19) {
        Ifranme = true;
      }
    } else {
      printf("not support format [%s]!\n", tmpCodecStr.c_str());
      return;
    }

    if (callback_) {
      EASY_FRAME_INFO_T frameinfo;
      memset(&frameinfo, 0x00, sizeof(EASY_FRAME_INFO_T));
      frameinfo.NaluType = u8NaluType;
      frameinfo.framesize = frameSize + 4;
      frameinfo.frameType = EASY_RTP_VIDEO;
      if (is264)
        frameinfo.frameform = EASY_RTP_H264;
      else
        frameinfo.frameform = EASY_RTP_H265;

      frameinfo.framebuff = fReceiveBufferTittle;
      frameinfo.tv_sec = presentationTime.tv_sec;
      frameinfo.tv_usec = presentationTime.tv_usec;
      frameinfo.bIFrame = Ifranme;
      callback_(frameinfo, userptr_);
    }
  }
  // // Then continue, to request the next frame of data:
  continuePlaying();
}
Boolean DummySink::continuePlaying() {
  if (fSource == NULL) return False;  // sanity check (should not happen)

  // Request the next frame of data from our input source.
  // "afterGettingFrame()" will get called later, when it arrives:
  fSource->getNextFrame(fReceiveBuffer, DUMMY_SINK_RECEIVE_BUFFER_SIZE,
                        afterGettingFrame, this, onSourceClosure, this);
  return True;
}
